﻿
#include <set>
#include <QApplication>
#include <QThread>
#include <QMessageBox>
#include "common.h"
#include "DlgBookRead.h"
#include "DlgSearchString.h"
#include <QRegExp>
#include <QClipboard>
#include <QTextDocument>

ThreadCacheBook::ThreadCacheBook(QWidget* parent) :MsgBoxThread(parent)
{
	connect(this, SIGNAL(finished()), (DlgBookRead*)parent, SLOT(on_slots_cache_finish()));
}

QByteArray ThreadCacheBook::get(const QString strUrl)
{
	assert(!strUrl.isEmpty());
	QSslConfiguration config;
	QNetworkRequest m_request;
	QNetworkAccessManager m_manager;
	config.setPeerVerifyMode(QSslSocket::VerifyNone);
	config.setProtocol(QSsl::TlsV1SslV3);
	m_request.setSslConfiguration(config);
	const QUrl url = strUrl;
	assert(url.isValid());
	m_request.setUrl(url);
	QNetworkReply* reply = m_manager.get(m_request); //m_qnam是QNetworkAccessManager对象
	QEventLoop eventLoop;
	QObject::connect(reply, &QNetworkReply::finished, &eventLoop, &QEventLoop::quit);
	eventLoop.exec(QEventLoop::ExcludeUserInputEvents);
	QObject::disconnect(reply, &QNetworkReply::finished, &eventLoop, &QEventLoop::quit);
	QByteArray replyData = reply->readAll();
	//int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
	//QVariant redirectAttr = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
	if (reply->error())
		//	|| 300 == statusCode) //状态码300 Multiple Choices，既不是错误也不算重定向，应该是qt bug
		//	|| !redirectAttr.isNull())
	{
		LOG_WRITE(LOG_LEVEL_WARN, (QString("网络异常:") + strUrl).toUtf8().data());
		replyData.clear();
	}

	// <2>检测网页返回状态码，常见是200，404等，200为成功
	int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
	qDebug() << "statusCode:" << statusCode;

	// <3>判断是否需要重定向
	if (statusCode >= 300 && statusCode < 400) {
		// redirect
		// 获取重定向信息
		const QVariant redirectionTarget = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
		// 检测是否需要重定向，如果不需要则读数据
		if (!redirectionTarget.isNull()) {
			const QUrl redirectedUrl = reply->url().resolved(redirectionTarget.toUrl());
			reply->deleteLater();
			reply = nullptr;
			auto&& ret = get(redirectedUrl.toString());
			qDebug() << "http redirect to " << redirectedUrl.toString();
			return ret;
		}
	}

	reply->deleteLater();
	reply = nullptr;
	return replyData;
}

void ThreadCacheBook::ChangeCharset(string& strText)
{
	string strCharset = GetCharset(strText);
	if (strCharset != "utf-8")
	{
		strText = ToUnicode(strText.c_str(), strCharset.c_str()).toStdString();
		//strText = ANSIToUTF8(strText.c_str()).data();
	}
}

int ThreadCacheBook::GetWebBookImage(sBookInfo& book, string& strText, bool bUpdateImageFile)
{
	QString strFilePathImage = BOOK_LIST_SAVE_DIR"cache/" + book.name + WEB_BOOK_IMAGE_FILE_NAME;
	ifstream infile(strFilePathImage.toLocal8Bit(), ios::in | ios::binary);
	if (!bUpdateImageFile && infile.good())
		return 0;
	QDir dir;
	dir.mkpath(BOOK_LIST_SAVE_DIR"cache/" + book.name);
	smatch sm;
	auto ret = regex_search(strText, sm, regex("\"([^\"]+\\.jpg)\""));
	if (ret && sm.size() >= 2)
	{
		QString strUrlImage = QString::fromStdString(sm[1]);
		strUrlImage = getUrlFullPath(book.path, strUrlImage);
	GET_FORWARD:
		QByteArray strImage = get(strUrlImage);
		if (strImage.size() > 0)
		{
			int isJpg = *(int*)strImage.left(3).data();
			QString strSuffix = strUrlImage.right(4);
			for (auto& c : strSuffix)
				c = c.unicode() | 32;
			if (strSuffix == ".jpg" && isJpg != 0xFFD8FF)
			{
				int head = strImage.indexOf("http");
				int tail = strImage.indexOf("\"", head + WEB_ADDRESS_HEAD_FIND_LENGTH);
				if (head != -1 && tail != -1)
				{
					strUrlImage = strImage.mid(head, tail - 1 - head).data();
					goto GET_FORWARD;
				}
				return -2;
			}
			ofstream file(strFilePathImage.toLocal8Bit(), ios::out | ios::binary);
			if (file)
			{
				file.write(strImage, strImage.size());
				file.close();
				return 0;
			}
		}
	}
	return -1;
}

int ThreadCacheBook::GetChapterListToFile(sBookInfo& book, vector<BookChapterList>& chapterList, string& strText)
{
	chapterList.clear();
	QString strFilePathList = BOOK_LIST_SAVE_DIR"cache/" + book.name;
	QDir dir;
	dir.mkpath(strFilePathList);
	strFilePathList += "/list.ini";
	//章头(,分隔符)章网页地址尾
	const char* Separator = ",";
	size_t head = 0;
	size_t map_index = 0;
	/// 索引，url，章头
	std::map<size_t, BookChapterList> map_index_url_chapter;
	//static const std::regex e("<a +[^>]*href *=['\" ]+([^'\"]+)['\" ]+[^>]*>[<a-z>]*([^<]+)[</a-z>]*</a>");
	static const std::regex e("<a +[^>]*href *=['\" ]+([^'\"]+)['\" ]+[^>]*>([^<]+)</a>");
	bool ret = false;
	book.ReadChapterInfo();
	bool set_chapter = (bool)book.chapter;
	do {
		std::cmatch cm;
		ret = std::regex_search(strText.c_str() + head, cm, e, std::regex_constants::match_default);
		if (ret && cm.size() == 3)
		{
			head += cm.position() + cm.length() + 1;
			auto&& strHead = QString::fromStdString(cm[2]);
			if (book.sort == Qt::CheckState::Checked)
			{
				map_index = chineseNum2num(strHead.toStdWString());
			}
			else
			{
				if (set_chapter == false && chineseNum2num(strHead.toStdWString()) == 1)
				{
					book.chapter = (int)map_index;
					book.strChapterHead = strHead;
					set_chapter = true;
				}
				map_index++;
			}
			if (map_index == 0)
				continue;
			auto it = map_index_url_chapter.find(map_index);
			if (it != map_index_url_chapter.end())
			{
				int curPriority = 0;
				if (strHead.indexOf("第") != -1)curPriority++;
				if (strHead.indexOf("章") != -1)curPriority++;
				int oldPriority = 0;
				if (it->second.head.indexOf("第") != -1)oldPriority++;
				if (it->second.head.indexOf("章") != -1)oldPriority++;
				if (oldPriority < curPriority)
					map_index_url_chapter[map_index] = BookChapterList("", strHead, QString::fromStdString(cm[1]));
			}
			else {
				map_index_url_chapter.emplace(map_index,
					BookChapterList("", strHead, QString::fromStdString(cm[1])));
			}
		}
		else
			break;
	} while (true);

	if (map_index_url_chapter.size() == 0)
	{
		LOG_WRITE(LOG_LEVEL_WARN, "获取章节列表错误,没有查找到章头");
		return -2;
	}

	// 这里重新写入 QStringList
	for (auto& [index, url_chapter] : map_index_url_chapter)
	{
		chapterList.emplace_back("", url_chapter.head, url_chapter.url);
	}

	book.maxChapter = (int)chapterList.size();
	if (book.chapter >= book.maxChapter)
	{
		book.chapter = 0;
		book.pos = 0;
	}
	book.strChapterHead = chapterList[book.chapter].head;

	//保存目录到文件
	ofstream fileWrite(strFilePathList.toLocal8Bit(), ios::out | ios::binary);
	if (fileWrite.good())
	{
		for (int i = 0; i < chapterList.size(); i++)
		{
			auto&& strChapter = chapterList[i].head.toUtf8();
			fileWrite.write(strChapter, strChapter.size());
			fileWrite.write(Separator, strlen(Separator));
			auto&& strUrl = chapterList[i].url.toUtf8();
			fileWrite.write(strUrl, strUrl.length());
			fileWrite.write("\n", 1);
		}
		fileWrite.close();
	}
	else
	{
		LOG_WRITE(LOG_LEVEL_WARN, "写章节内容到文件出错, %s\n", strFilePathList.toUtf8().data());
		return -3;
	}
	return 0;
}

int ThreadCacheBook::GetChapterList(sBookInfo& book, vector<BookChapterList>& chapterList,
	bool bUpdateListFile)
{
	QString strFilePathList = BOOK_LIST_SAVE_DIR"cache/" + book.name;
	QDir dir;
	dir.mkpath(strFilePathList);
	strFilePathList += "/list.ini";
	//章头(,分隔符)章网页地址尾
	const char* Separator = ",";
	if (!bUpdateListFile)
	{
		ifstream fileRead(strFilePathList.toLocal8Bit(), ios::in | ios::binary);
		if (fileRead.good())
		{
			chapterList.clear();
			string strline;
			while (getline(fileRead, strline))
			{
				QString strLine(QString::fromUtf8(strline.c_str()));
				int index = strLine.lastIndexOf(Separator);
				if (index != -1)
				{
					chapterList.emplace_back("", strLine.left(index));
					index++;
					chapterList.back().url = strLine.mid(index, strLine.length() - index);
				}
			}
			fileRead.close();
			if (!chapterList.empty())
			{
				book.maxChapter = (int)chapterList.size();
				return 0;
			}
		}
	}
	string strText = get(book.path).data();
	//获取目录列表,填充目录窗口,获取当前章文本显示
	if (strText.empty())
	{
		LOG_WRITE(LOG_LEVEL_WARN, "获取章节列表错误,内容为空");
		return -1;
	}
	ChangeCharset(strText);
	GetWebBookInfo(strText, book);
	GetChapterListToFile(book, chapterList, strText);

	int iRet = GetWebBookImage(book, strText, true);
	if (iRet != 0)
	{
		LOG_WRITE(LOG_LEVEL_WARN, "获取书图片失败, %s\n", book.name.toUtf8().data());
		return 1;
	}

	return 0;
}

auto ThreadCacheBook::Html2Text(const QString& strText)
{
	// 创建一个文本文档
	QTextDocument document;
	// 设置带有 HTML 格式的文本内容
	document.setHtml(strText);
	// 获取带有 HTML 格式的文本内容
	return document.toPlainText();
}

int ThreadCacheBook::GetContent(QString& strText)
{

	auto func_get_text_with_row_max = [&]() {
		auto strTextTemp = strText;
		auto&& strlist = strTextTemp.split("\n");
		if (strlist.size() > 0)
		{
			int len = 0;
			QString* pstr = &strlist[0];
			for (auto& line : strlist)
			{
				if (len < line.size()) {
					len = line.size();
					pstr = &line;
				}
			}
			strTextTemp = *pstr;
			strTextTemp = Html2Text(strTextTemp);
		}
		else
		{
			LOG_WRITE(LOG_LEVEL_WARN, "章节内容获取错误");
			return -1;
		}
		return RetFilterString(strText, strTextTemp);
		};

	auto func_get_text = [&](const QString& strHead) {
		auto strTextTemp = strText;
		auto head = strTextTemp.lastIndexOf(strHead);
		if (head != -1)
		{
			const auto start = strTextTemp.indexOf(">", head) + 1;
			head = strTextTemp.lastIndexOf("<", head) + 1;
			auto space = strTextTemp.indexOf(" ", head);
			QString flag(strTextTemp.mid(head, space - head));
			flag = "</" + flag + ">";
			auto tail = strTextTemp.indexOf(flag, head);
			if (tail == -1)
			{
				LOG_WRITE(LOG_LEVEL_WARN, "章节内容解析错误");
				return -3;
			}
			head = start;
			tail--;
			strTextTemp = strTextTemp.mid(head, tail - head);
			strTextTemp = Html2Text(strTextTemp);
		}
		else
		{
			LOG_WRITE(LOG_LEVEL_WARN, "章节内容获取错误");
			return -1;
		}
		return RetFilterString(strText, strTextTemp);
		};

	auto func_get_text_for_head_end = [&](const QString& strHead, const QString& strEnd) {
		auto strTextTemp = strText;
		auto head = strTextTemp.indexOf(strHead);
		if (head != -1)
		{
			auto tail = strTextTemp.indexOf(strEnd, head);
			if (tail == -1)
			{
				LOG_WRITE(LOG_LEVEL_WARN, "章节内容解析错误");
				return -3;
			}
			strTextTemp = strTextTemp.mid(head, tail - head);
			strTextTemp = Html2Text(strTextTemp);
			//strTextTemp.replace("&nbsp;", " ");
			//strTextTemp.replace(QRegularExpression("<[brp /]*>"), "\n");
			//strTextTemp.replace(QRegularExpression("<[^>]*>"), "\t");
			//while ((head = strTextTemp.indexOf('<')) != -1 && (tail = strTextTemp.indexOf('>', head)) != -1)
			//	strTextTemp.remove(head, tail - head + 1);
		}
		else
		{
			LOG_WRITE(LOG_LEVEL_WARN, "章节内容获取错误");
			return -1;
		}
		return RetFilterString(strText, strTextTemp);
		};

	auto func_html_to_text = [&]() {
		auto strTextTemp = Html2Text(strText);
		auto head = strTextTemp.indexOf(QRegularExpression("推荐.*："));
		if (head == -1)
			head = strTextTemp.indexOf("：");
		if (head == -1)
			head = strTextTemp.indexOf("章");
		if (head != -1)
		{
			head = strTextTemp.indexOf("\n", head);
			if (head != -1)
			{
				head++;
			}
		}
		else {
			head = 0;
		}
		auto tail = strTextTemp.lastIndexOf("上一章");
		if (tail == -1)
			tail = strTextTemp.lastIndexOf("章", tail);
		if (tail != -1)
			tail = strTextTemp.lastIndexOf("\n", tail);
		if (tail != -1)
			strTextTemp = strTextTemp.mid(head, ++tail - head);
		else
			strTextTemp = strTextTemp.mid(head);
		return RetFilterString(strText, strTextTemp);
		};

	int ret = 0;
	if (((ret = func_html_to_text()) != 0)
		&& ((ret = func_get_text_for_head_end("contentbox", "</div>")) != 0)
		&& ((ret = func_get_text("\"content\"")) != 0)
		&& ((ret = func_get_text("\"booktxt\"")) != 0)
		&& ((ret = func_get_text_with_row_max()) != 0))
		return ret;
	return 0;
}

int ThreadCacheBook::GetChapterText(sBookInfo& book, BookChapterList& chapterList,
	bool bReadFile, bool bUpdateFile)
{
	if (chapterList.head.isEmpty())
	{
		LOG_WRITE(LOG_LEVEL_WARN, "章头为空");
		return -1;
	}
	QString strChapterFilePath = chapterList.head;
	ModifySymbol(strChapterFilePath);
	QString strFilePathChapter = BOOK_LIST_SAVE_DIR"cache/" + book.name;
	//QDir dir;
	//dir.mkpath(strFilePathChapter);
	strFilePathChapter += '/' + strChapterFilePath + ".chapter";
	if (!bUpdateFile)
	{
		ifstream file(strFilePathChapter.toLocal8Bit(), ios::in | ios::binary);
		if (file.good())
		{
			if (bReadFile)
			{
				string content((istreambuf_iterator<char>(file)),
					(istreambuf_iterator<char>()));
				QString&& strText = QString::fromUtf8(content.c_str());
				file.close();
				auto len = strText.length();
				FilterString(strText);
				auto len2 = strText.length();
				if (len != len2)
				{
					//更新并重新保存章节文件
					ofstream fileWrite(strFilePathChapter.toLocal8Bit(), ios::out | ios::binary);
					if (fileWrite.good())
					{
						auto&& strWrite = strText.toUtf8();
						fileWrite.write(strWrite, strWrite.size());
						fileWrite.close();
					}
					else
					{
						LOG_WRITE(LOG_LEVEL_WARN, "写章节内容到文件出错, %s", strChapterFilePath.toUtf8().data());
						return -2;
					}
				}
				chapterList.text = strText;
				return 0;
			}
			return 0;
		}
	}

	QString strTextAll;
	auto url = chapterList.url;
	do
	{
		string str = get(getUrlFullPath(book.path, url)).data();
		if (str.empty())
		{
			LOG_WRITE(LOG_LEVEL_WARN, "章节内容为空");
			return -1;
		}
		QString strText;
		{
			string strCharset = GetCharset(str);
			if (strCharset == "utf-8")
			{
				strText = QString::fromStdString(str.c_str());
			}
			else
			{
				strText = ToUnicode(str.c_str(), strCharset.c_str());
				//strText = ANSIToUTF8(str.c_str()).data();
			}
		}
		auto ret = 0;
		if ((ret = GetContent(strText)) != 0)
			return ret;
		strTextAll += strText;
		smatch sm;
		url.clear();
		ret = regex_search(str, sm, regex("<a [^>]*href *=\"([^\"]+)\">下一页</a>"));
		if (ret && sm.size() >= 2)
			url = QString::fromStdString(sm[1]);
		else
			break;
	} while (1);

	chapterList.text = strTextAll;
	//保存章节文件
	ofstream fileWrite(strFilePathChapter.toLocal8Bit(), ios::out | ios::binary);
	if (fileWrite.good())
	{
		auto&& strWrite = strTextAll.toUtf8();
		fileWrite.write(strWrite, strWrite.size());
		fileWrite.close();
	}
	else
	{
		LOG_WRITE(LOG_LEVEL_WARN, "写章节内容到文件出错, %s", strChapterFilePath.toUtf8().data());
		return -2;
	}
	return 0;
}

void ThreadCacheBook::run()
{
	sBookInfo book = m_listBookInfo[0];
	auto& chapterList = ((DlgBookRead*)parent())->m_chapterList;
	int count = 0;
	list<int> listFail;
	int index = 0;
	for (index = book.chapter + 1; index < chapterList.size(); )
	{
		if (m_stop)
			break;
		int iRet = GetChapterText(book, chapterList[index]);
		if (iRet == 0)
		{
			index++;
			count = 0;
		}
		else
		{
			count++;
			if (count > g_get_web_repeat_times)
			{
				listFail.emplace_back(index);
				LOG_WRITE(LOG_LEVEL_WARN, "缓存错误, 获取缓存重复次数超限, 章：%s",
					chapterList[index].head.toUtf8().constData());
				index++;
				count = 0;
				//有n次失败就终止
				if (listFail.size() >= g_get_web_repeat_times)
					break;
			}
		}
	}
	if (index >= chapterList.size())
		index = (int)chapterList.size() - 1;
	auto& strInfo = ((DlgBookRead*)parent())->msg_cache_finish;
	strInfo = "";
	if (listFail.size() > 0)
	{
		strInfo = "缓存错误\n" + tr("获取缓存重复次数超限, 缓存到：\n") +
			chapterList[index].head + "\n" + book.path + "\n";
		for (auto& index_fail : listFail)
		{
			strInfo += chapterList[index_fail].head + "\n";
		}
		LOG_WRITE(LOG_LEVEL_WARN, "缓存错误, 获取缓存重复次数超限");
		if (((DlgBookRead*)parent())->isVisible())
		{
			// 设置到剪贴板
			QClipboard* clip = QApplication::clipboard();
			clip->setText(strInfo);
		}
		return;
	}
	else
	{
		if (index >= chapterList.size() - 1)
			strInfo = tr("缓存完毕\n");
		if (index >= 0 && index < chapterList.size())
			strInfo += tr("缓存到：\n") + chapterList[index].head
			+ "\n" + book.path + "\n";
	}
}

QString& ThreadCacheBook::FilterStringErase(QString& strChapterText, const wchar_t s, const wchar_t e)
{
	int head = 0, tail = 0;
	while (-1 != (head = strChapterText.indexOf(s, head)))
	{
		if (-1 != (tail = strChapterText.indexOf(e, head)))
		{
			strChapterText.remove(head, tail - head + 1);
		}
		else
			break;
	}
	return strChapterText;
}

QString& ThreadCacheBook::FilterString(QString& strChapterText)
{
	std::wstring str(strChapterText.toStdWString());
	for (auto& reg : g_mapRegularFilter)
	{
		std::wregex e(reg.first);
		str = std::regex_replace(str, e, reg.second);
	}
	strChapterText = QString::fromStdWString(str);
	return strChapterText;
}

int ThreadCacheBook::RetFilterString(QString& strText, QString& strTextTemp)
{
	ThreadCacheBook::FilterString(strTextTemp);
	if (strTextTemp.size() < MIN_WRITE_CHAPTER_SIZE)
	{
		return 2;
	}
	strText = strTextTemp;
	return 0;
}
